package bma.common.langutil.ai.callback;

import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;

import bma.common.langutil.core.Preconditions;

/**
 * 
 * @author guanzhong
 * @since 1.0 (imported from BMA.ESP Project)
 * 
 * @param <RESP_TYPE>
 */
public class AICallbackCore<RESP_TYPE> implements AICallbackGettable<RESP_TYPE> {

	/** Synchronization control for AbstractFutures. */
	protected final Sync<RESP_TYPE> sync = new Sync<RESP_TYPE>();

	public RESP_TYPE get(long timeout, TimeUnit unit)
			throws InterruptedException, TimeoutException, ExecutionException {
		return sync.get(unit.toNanos(timeout));
	}

	@Override
	public RESP_TYPE get() throws InterruptedException, ExecutionException {
		return sync.get();
	}

	@Override
	public boolean isDone() {
		return sync.isDone();
	}

	@Override
	public void onSuccess(RESP_TYPE result) {
		set(result);
	}

	protected boolean set(RESP_TYPE value) {
		boolean result = sync.set(value);
		if (result) {
			done();
		}
		return result;
	}

	@Override
	public void onFailure(Throwable t) {
		setException(t);
	}

	protected boolean setException(Throwable throwable) {
		boolean result = sync.setException(Preconditions
				.checkNotNull(throwable));
		if (result) {
			done();
		}

		// If it's an Error, we want to make sure it reaches the top of the
		// call stack, so we rethrow it.
		if (throwable instanceof Error) {
			throw (Error) throwable;
		}
		return result;
	}

	protected void done() {
	}

	protected static class Sync<RESP_TYPE> extends AbstractQueuedSynchronizer {

		private static final long serialVersionUID = 0L;

		/* Valid states. */
		static final int RUNNING = 0;
		static final int COMPLETING = 1;
		static final int COMPLETED = 2;

		private RESP_TYPE value;
		private Throwable exception;

		/*
		 * Acquisition succeeds if the future is done, otherwise it fails.
		 */
		@Override
		protected int tryAcquireShared(int ignored) {
			if (isDone()) {
				return 1;
			}
			return -1;
		}

		/*
		 * We always allow a release to go through, this means the state has
		 * been successfully changed and the result is available.
		 */
		@Override
		protected boolean tryReleaseShared(int finalState) {
			setState(finalState);
			return true;
		}

		/**
		 * Blocks until the task is complete or the timeout expires. Throws a
		 * {@link TimeoutException} if the timer expires, otherwise behaves like
		 * {@link #get()}.
		 */
		RESP_TYPE get(long nanos) throws TimeoutException, ExecutionException,
				InterruptedException {

			// Attempt to acquire the shared lock with a timeout.
			if (!tryAcquireSharedNanos(-1, nanos)) {
				throw new TimeoutException("Timeout waiting for task.");
			}

			return getValue();
		}

		/**
		 * Blocks until {@link #complete(Object, Throwable, int)} has been
		 * successfully called. Throws a {@link CancellationException} if the
		 * task was cancelled, or a {@link ExecutionException} if the task
		 * completed with an error.
		 */
		RESP_TYPE get() throws ExecutionException, InterruptedException {

			// Acquire the shared lock allowing interruption.
			acquireSharedInterruptibly(-1);
			return getValue();
		}

		/**
		 * Implementation of the actual value retrieval. Will return the value
		 * on success, an exception on failure, a cancellation on cancellation,
		 * or an illegal state if the synchronizer is in an invalid state.
		 */
		private RESP_TYPE getValue() throws ExecutionException {
			int state = getState();
			switch (state) {
			case COMPLETED:
				if (exception != null) {
					throw new ExecutionException(exception);
				} else {
					return value;
				}

			default:
				throw new IllegalStateException(
						"Error, synchronizer in invalid state: " + state);
			}
		}

		/**
		 * Checks if the state is {@link #COMPLETED} or {@link #CANCELLED}.
		 */
		boolean isDone() {
			return (getState() & COMPLETED) != 0;
		}

		/**
		 * Transition to the COMPLETED state and set the value.
		 */
		boolean set(RESP_TYPE v) {
			return complete(v, null, COMPLETED);
		}

		/**
		 * Transition to the COMPLETED state and set the exception.
		 */
		boolean setException(Throwable t) {
			return complete(null, t, COMPLETED);
		}

		/**
		 * Implementation of completing a task. Either {@code v} or {@code t}
		 * will be set but not both. The {@code finalState} is the state to
		 * change to from {@link #RUNNING}. If the state is not in the RUNNING
		 * state we return {@code false}.
		 * 
		 * @param v
		 *            the value to set as the result of the computation.
		 * @param t
		 *            the exception to set as the result of the computation.
		 * @param finalState
		 *            the state to transition to.
		 */
		private boolean complete(RESP_TYPE v, Throwable t, int finalState) {
			if (compareAndSetState(RUNNING, COMPLETING)) {
				this.value = v;
				this.exception = t;
				releaseShared(finalState);
				return true;
			}

			// The state was not RUNNING, so there are no valid transitions.
			return false;
		}

	}
}
